package com.siashan.toolkit.crypt.binary;

import com.siashan.toolkit.crypt.BinaryDecoder;
import com.siashan.toolkit.crypt.BinaryEncoder;
import com.siashan.toolkit.crypt.DecoderException;
import com.siashan.toolkit.crypt.EncoderException;

import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;

/**
 * 十六进制转换工具类
 *
 * @author siashan
 * @since 1.0.7
 */
public class Hex implements BinaryEncoder, BinaryDecoder {

    /**
     * 默认字符编码
     *
     * @since 1.0.7
     */
    public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;

    /**
     * 默认字符编码名称
     *
     * @since 1.0.7
     */
    public static final String DEFAULT_CHARSET_NAME = CharEncoding.UTF_8;

    /**
     * 用于将输出构建为十六进制字符串
     */
    private static final char[] DIGITS_LOWER = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd',
            'e', 'f'};

    /**
     * 用于将输出构建为十六进制字符串
     */
    private static final char[] DIGITS_UPPER = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D',
            'E', 'F'};

    /**
     * 十六进制解码
     *
     * @param data 十六进制字节数组
     * @return 解码后的字节数组.
     * @throws DecoderException 解码失败异常
     */
    public static byte[] decodeHex(final char[] data) throws DecoderException {
        final byte[] out = new byte[data.length >> 1];
        decodeHex(data, out, 0);
        return out;
    }

    /**
     * 十六进制解码
     *
     * 将表示十六进制值的字符数组转换为具有相同值的字节数组
     * 返回的数组将是传递的数组长度的一半，因为它需要两个字符来表示任何给定的字节
     * 如果传递的char数组具有奇数个元素，则引发异常。
     *
     * @param data 十六进制字节数组
     * @param out 一个字节数组，包含从提供的字符数组解码的二进制数据.
     * @param outOffset 开始写入解码字节的位置
     * @return 写入{@code out}的字节数.
     * @throws DecoderException 如果提供奇数个字符或非法字符，则引发异常
     * @since 1.0.7
     */
    public static int decodeHex(final char[] data, final byte[] out, final int outOffset) throws DecoderException {
        final int len = data.length;

        if ((len & 0x01) != 0) {
            throw new DecoderException("Odd number of characters.");
        }

        final int outLen = len >> 1;
        if (out.length - outOffset < outLen) {
            throw new DecoderException("Output array is not large enough to accommodate decoded data.");
        }

        // two characters form the hex value.
        for (int i = outOffset, j = 0; j < len; i++) {
            int f = toDigit(data[j], j) << 4;
            j++;
            f = f | toDigit(data[j], j);
            j++;
            out[i] = (byte) (f & 0xFF);
        }

        return outLen;
    }

    /**
     * 十六进制解码
     *
     * 将表示十六进制值的字符串转换为具有相同值的字节数组。
     * 返回的数组将是传递字符串长度的一半，因为它需要两个字符来表示任何给定的字节。
     * 如果传递的字符串包含奇数个元素，则引发异常。
     *
     * @param data 十六进制字符串
     * @return 包含从提供的字符数组解码的二进制数据的字节数组。
     * @throws DecoderException 如果提供奇数个字符或非法字符，则引发异常
     * @since 1.0.7
     */
    public static byte[] decodeHex(final String data) throws DecoderException {
        return decodeHex(data.toCharArray());
    }

    /**
     * 十六进制加密
     *
     * 将字节数组转换为按顺序表示每个字节的十六进制值的字符数组。
     * 返回的数组长度将是传递的数组长度的两倍，因为表示任何给定字节需要两个字符。
     *
     * @param data 要转换为十六进制字符的字节数组
     * @return 包含小写十六进制字符的字符数组
     * @since 1.0.7
     */
    public static char[] encodeHex(final byte[] data) {
        return encodeHex(data, true);
    }

    /**
     * 十六进制加密
     *
     * 将字节数组转换为按顺序表示每个字节的十六进制值的字符数组。
     * 返回的数组长度将是传递的数组长度的两倍，因为表示任何给定字节需要两个字符。
     *
     * @param data       要转换为十六进制字符的字节数组
     * @param toLowerCase {@code true} 转换为小写, {@code false} 转大写
     * @return 在所选大小写中包含十六进制字符的字符数组
     * @since 1.0.7
     */
    public static char[] encodeHex(final byte[] data, final boolean toLowerCase) {
        return encodeHex(data, toLowerCase ? DIGITS_LOWER : DIGITS_UPPER);
    }

    /**
     * 十六进制加密
     *
     * 将字节数组转换为按顺序表示每个字节的十六进制值的字符数组。
     * 返回的数组长度将是传递的数组长度的两倍，因为表示任何给定字节需要两个字符。
     *
     * @param data     要转换为十六进制字符的字节数组
     * @param toDigits 输出字母表（必须至少包含16个字符）
     * @return 字符[]包含字母表中的适当字符以获得最佳结果，应为大写或小写十六进制。
     * @since 1.0.7
     */
    protected static char[] encodeHex(final byte[] data, final char[] toDigits) {
        final int l = data.length;
        final char[] out = new char[l << 1];
        encodeHex(data, 0, data.length, toDigits, out, 0);
        return out;
    }

    /**
     * 十六进制加密
     *
     * 将字节数组转换为按顺序表示每个字节的十六进制值的字符数组。
     *
     * @param data  要转换为十六进制字符的字节数组
     * @param dataOffset {@code data}中开始编码的位置
     * @param dataLen 从{@code dataOffset}到编码的字节数
     * @param toLowerCase {@code true}转换为小写，{@code false}转换为大写
     * @return 字符[]包含字母表中的适当字符以获得最佳结果，应为大写或小写十六进制。
     * @since 1.0.7
     */
    public static char[] encodeHex(final byte[] data, final int dataOffset, final int dataLen,
                                   final boolean toLowerCase) {
        final char[] out = new char[dataLen << 1];
        encodeHex(data, dataOffset, dataLen, toLowerCase ? DIGITS_LOWER : DIGITS_UPPER, out, 0);
        return out;
    }

    /**
     * 十六进制加密
     *
     * 将字节数组转换为按顺序表示每个字节的十六进制值的字符数组。
     *
     * @param data 要转换为十六进制字符的字节数组
     * @param dataOffset {@code data}中开始编码的位置
     * @param dataLen 从{@code dataOffset}到编码的字节数
     * @param toLowerCase {@code true}转换为小写，{@code false}转换为大写
     * @param out 一个字符[]，它将保存字母表中的相应字符。
     * @param outOffset {@code out}内开始写入编码字符的位置。
     * @since 1.0.7
     */
    public static void encodeHex(final byte[] data, final int dataOffset, final int dataLen,
                                 final boolean toLowerCase, final char[] out, final int outOffset) {
        encodeHex(data, dataOffset, dataLen, toLowerCase ? DIGITS_LOWER : DIGITS_UPPER, out, outOffset);
    }

    /**
     * 十六进制加密
     *
     * 将字节数组转换为按顺序表示每个字节的十六进制值的字符数组。
     *
     * @param data 要转换为十六进制字符的字节数组
     * @param dataOffset {@code data}中开始编码的位置
     * @param dataLen 从{@code dataOffset}到编码的字节数
     * @param toDigits 输出字母表（必须至少包含16个字符）
     * @param out 一个字符[]，它将保存字母表中的相应字符。
     * @param outOffset  {@code out}内开始写入编码字符的位置。
     */
    private static void encodeHex(final byte[] data, final int dataOffset, final int dataLen, final char[] toDigits,
                                  final char[] out, final int outOffset) {
        // two characters form the hex value.
        for (int i = dataOffset, j = outOffset; i < dataOffset + dataLen; i++) {
            out[j++] = toDigits[(0xF0 & data[i]) >>> 4];
            out[j++] = toDigits[0x0F & data[i]];
        }
    }

    /**
     * 十六进制加密
     *
     * 将字节缓冲区转换为按顺序表示每个字节的十六进制值的字符数组。
     * 返回的数组的长度将是传递的数组的两倍，因为它需要两个字符来表示任何给定的字节。
     *
     * <p>将使用{@link ByteBuffer#remaining（）}标识的所有字节；
     * 使用此方法后{@link ByteBuffer#remaining（）remaining（）}的值将为零。</p>
     *
     * @param data 要转换为十六进制字符的字节数组
     * @return 包含小写十六进制字符的字符[]
     * @since 1.0.7
     */
    public static char[] encodeHex(final ByteBuffer data) {
        return encodeHex(data, true);
    }

    /**
     * 十六进制加密
     *
     * 将字节缓冲区转换为按顺序表示每个字节的十六进制值的字符数组。
     * 返回的数组的长度将是传递的数组的两倍，因为它需要两个字符来表示任何给定的字节。
     *
     * <p>将使用{@link ByteBuffer#remaining（）}标识的所有字节；
     *      * 使用此方法后{@link ByteBuffer#remaining（）remaining（）}的值将为零。</p>
     *
     * @param data        要转换为十六进制字符的字节数组
     * @param toLowerCase {@code true}转换为小写，{@code false}转换为大写
     * @return 在所选大小写中包含十六进制字符的字符数组
     * @since 1.0.7
     */
    public static char[] encodeHex(final ByteBuffer data, final boolean toLowerCase) {
        return encodeHex(data, toLowerCase ? DIGITS_LOWER : DIGITS_UPPER);
    }

    /**
     * 十六进制加密
     *
     * 将字节缓冲区转换为按顺序表示每个字节的十六进制值的字符数组。
     * 返回的数组的长度将是传递的数组的两倍，因为它需要两个字符来表示任何给定的字节。
     *
     * <p>将使用{@link ByteBuffer#remaining（）}标识的所有字节；
     * 使用此方法后{@link ByteBuffer#remaining（）remaining（）}的值将为零。</p>
     *
     * @param byteBuffer 用于转换为十六进制字符的字节缓冲区
     * @param toDigits   输出字母表（必须至少包含16个字符）
     * @return 字符[]包含字母表中的适当字符以获得最佳结果，应为大写或小写十六进制。
     * @since 1.0.7
     */
    protected static char[] encodeHex(final ByteBuffer byteBuffer, final char[] toDigits) {
        return encodeHex(toByteArray(byteBuffer), toDigits);
    }

    /**
     * 十六进制加密
     *
     * 将字节数组转换为按顺序表示每个字节的十六进制值的字符串。返回
     * 字符串的长度将是传递数组的两倍，因为它需要两个字符来表示任何给定的字节。
     *
     * @param data 要转换为十六进制字符的字节[]
     * @return 小写十六进制字符包含小写十六进制字符的字符串
     * @since 1.0.7
     */
    public static String encodeHexString(final byte[] data) {
        return new String(encodeHex(data));
    }

    /**
     * 十六进制加密
     *
     * 将字节数组转换为按顺序表示每个字节的十六进制值的字符串。返回
     * 字符串的长度将是传递数组的两倍，因为它需要两个字符来表示任何给定的字节。
     *
     * @param data        要转换为十六进制字符的字节[]
     * @param toLowerCase {@code true} 转换为小写, {@code false} 转换为大写
     * @return 小写十六进制字符包含小写十六进制字符的字符串
     * @since 1.0.7
     */
    public static String encodeHexString(final byte[] data, final boolean toLowerCase) {
        return new String(encodeHex(data, toLowerCase));
    }

    /**
     * 十六进制加密
     *
     * @param data 要转换为十六进制字符的字节[]
     * @return 小写十六进制字符包含小写十六进制字符的字符串
     * @since 1.0.7
     */
    public static String encodeHexString(final ByteBuffer data) {
        return new String(encodeHex(data));
    }

    /**
     * 十六进制加密
     *
     * @param data    要转换为十六进制字符的字节[]
     * @param toLowerCase {@code true} 转换为小写, {@code false} 转换为大写
     * @return 小写十六进制字符包含小写十六进制字符的字符串
     * @since 1.0.7
     */
    public static String encodeHexString(final ByteBuffer data, final boolean toLowerCase) {
        return new String(encodeHex(data, toLowerCase));
    }

    /**
     * 将字节缓冲区转换为字节数组
     *
     * @param byteBuffer 字节缓冲区
     * @return 字节数组
     */
    private static byte[] toByteArray(final ByteBuffer byteBuffer) {
        final int remaining = byteBuffer.remaining();
        // Use the underlying buffer if possible
        if (byteBuffer.hasArray()) {
            final byte[] byteArray = byteBuffer.array();
            if (remaining == byteArray.length) {
                byteBuffer.position(remaining);
                return byteArray;
            }
        }
        // Copy the bytes
        final byte[] byteArray = new byte[remaining];
        byteBuffer.get(byteArray);
        return byteArray;
    }

    /**
     * 将十六进制字符转换为整数
     *
     * @param ch    十六进制字符
     * @param index 字符索引
     * @return 整数
     * @throws DecoderException 如果ch是非法的十六进制字符，则引发异常
     */
    protected static int toDigit(final char ch, final int index) throws DecoderException {
        final int digit = Character.digit(ch, 16);
        if (digit == -1) {
            throw new DecoderException("Illegal hexadecimal character " + ch + " at index " + index);
        }
        return digit;
    }

    private final Charset charset;

    /**
     * 构造方法
     */
    public Hex() {
        // use default encoding
        this.charset = DEFAULT_CHARSET;
    }

    /**
     * 构造方法
     *
     * @param charset 字符编码.
     * @since 1.0.7
     */
    public Hex(final Charset charset) {
        this.charset = charset;
    }

    /**
     * 构造方法.
     *
     * @param charsetName 字符编码名称.
     * @throws java.nio.charset.UnsupportedCharsetException 不支持的字符编码异常
     * @since 1.0.7
     */
    public Hex(final String charsetName) {
        this(Charset.forName(charsetName));
    }

    /**
     * 将表示十六进制值的字符字节数组转换为具有相同值的字节数组。
     * 返回的数组将是传递的数组长度的一半，因为表示任何给定字节需要两个字符。
     * 如果传递的char数组具有奇数个元素，则引发异常。
     *
     * @param array 包含十六进制数字的字符字节数组
     * @return 包含从提供的字节数组（表示字符）解码的二进制数据的字节数组。
     * @throws DecoderException 如果向此函数提供奇数个字符，则引发
     * @see #decodeHex(char[])
     */
    @Override
    public byte[] decode(final byte[] array) throws DecoderException {
        return decodeHex(new String(array, getCharset()).toCharArray());
    }

    /**
     * 将表示十六进制值的字符字节缓冲区转换为具有相同值的字节数组。
     * 返回的数组长度将是传递的数组长度的一半，因为表示任何给定字节需要两个字符。
     * 如果传递的char数组具有奇数个元素，则引发异常
     *
     * <p>将使用{@link ByteBuffer#remaining（）}标识的所有字节；
     * 使用此方法后{@link ByteBuffer#remaining（）remaining（）}的值将为零.</p>
     *
     * @param buffer 十六进制字节缓冲区
     * @return 解码后的字节数组
     * @throws DecoderException 如果字符个数为奇数个则引发
     * @since 1.0.7
     */
    public byte[] decode(final ByteBuffer buffer) throws DecoderException {
        return decodeHex(new String(toByteArray(buffer), getCharset()).toCharArray());
    }

    /**
     * 将表示十六进制值的字符串或字符字节数组转换为具有相同值的字节数组。
     * 返回的数组将是传递的字符串或数组长度的一半，因为它需要两个字符来表示任何给定的字节。
     * 如果传递的char数组具有奇数个元素，则引发异常。
     *
     * @param object 字符串、字节缓冲符、字节[]或包含十六进制数字的字符字节数组
     * @return 包含从提供的字节数组（表示字符）解码的二进制数据的字节数组。
     * @throws DecoderException 如果向此函数提供奇数个字符，或者对象不是字符串或字符[]，则引发
     * @see #decodeHex(char[])
     * @since 1.0.7
     */
    @Override
    public Object decode(final Object object) throws DecoderException {
        if (object instanceof String) {
            return decode(((String) object).toCharArray());
        } else if (object instanceof byte[]) {
            return decode((byte[]) object);
        } else if (object instanceof ByteBuffer) {
            return decode((ByteBuffer) object);
        } else {
            try {
                return decodeHex((char[]) object);
            } catch (final ClassCastException e) {
                throw new DecoderException(e.getMessage(), e);
            }
        }
    }

    /**
     *
     * 将字节数组转换为按顺序表示每个字节的十六进制值的字符的字节数组。
     * 返回的数组的长度将是传递的数组的两倍，因为它需要两个字符来表示任何给定的字节。
     *
     * @param array 需要转换成十六进制的字节数组
     * @return 十六进制小写字节数组
     * @since 1.0.7
     */
    @Override
    public byte[] encode(final byte[] array) {
        return encodeHexString(array).getBytes(this.getCharset());
    }

    /**
     * 将字节缓冲区转换为按顺序表示每个字节的十六进制值的字符的字节数组。
     * 返回的数组的长度将是传递的数组的两倍，因为它需要两个字符来表示任何给定的字节。
     *
     * @param array 用于转换为十六进制字符的字节缓冲区
     * @return 包含小写十六进制字符字节的字节[]
     * @since 1.0.7
     */
    public byte[] encode(final ByteBuffer array) {
        return encodeHexString(array).getBytes(this.getCharset());
    }

    /**
     * 将字符串或字节数组转换为按顺序表示每个字节的十六进制值的字符数组。
     * 返回的数组将是传递的字符串或数组长度的两倍，因为它需要两个字符来表示任何给定的字节。
     *
     * @param object 需要加密的数据
     * @return 解密结果 小写字节数组
     * @throws EncoderException 加密异常
     */
    @Override
    public Object encode(final Object object) throws EncoderException {
        byte[] byteArray;
        if (object instanceof String) {
            byteArray = ((String) object).getBytes(this.getCharset());
        } else if (object instanceof ByteBuffer) {
            byteArray = toByteArray((ByteBuffer) object);
        } else {
            try {
                byteArray = (byte[]) object;
            } catch (final ClassCastException e) {
                throw new EncoderException(e.getMessage(), e);
            }
        }
        return encodeHex(byteArray);
    }

    /**
     * 获取字符编码.
     *
     * @return 字符编码.
     * @since 1.0.7
     */
    public Charset getCharset() {
        return this.charset;
    }

    /**
     * 获取字符编码名称.
     *
     * @return 字符编码名称.
     * @since 1.0.7
     */
    public String getCharsetName() {
        return this.charset.name();
    }

    @Override
    public String toString() {
        return super.toString() + "[charsetName=" + this.charset + "]";
    }
}
